【手写系列】call,apply与bind以及详细解析

The局外人 Lv Max

关于手写 call,apply 与 bind,我有如下总结,因为我也是看完课程后的总结。所以学之前,先建议看看黑马新出的前端面试题中关于call,apply与bind,一起来看看吧~~~

call

call 是一个函数,可以改变函数的this 指向,先来看看 call 的基本使用吧!

1
2
3
4
5
6
7
8
9
let obj = { name: "刘德华" };

function fn(num1, num2) {
console.log(this);
return num1 + num2;
}

const res = fn.call(obj, 1, 2);
console.log("两数之和为", res);

可以看出 call 函数改变了 fn 函数的 this 指向变为 obj,并且可以传递参数序列

第一步(改变 this)

接下来我将定义一个函数 myCall 来实现 call 所具有的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//因为只有函数才能调用call方法,所以把myCall放到Function构造函数的原型上,来供所有的实例函数使用。
//这里outObj,args分别为,要把原函数this改为outObj和参数序列
Function.prototype.myCall = function (outObj, ...args) {
//这里我在outObj对象上追加一属性f把this赋值给f (这里this指向函数调用者fn)
outObj.f = this;
//f在其中充当一个转接变量,outObj.f()相当于outObj直接调用了fn函数,
//由此可得出fn里的this就会指向outObj,也就是obj
outObj.f();
};
let obj = { name: "刘德华" };
function fn(num1, num2) {
console.log(this);
return num1 + num2;
}
const res = fn.myCall(obj, 1, 2);
console.log("两数之和为", res);

详细讲解,看上方代码注释!!!上述代码的 f (中转键),有可能与传入对象重名,这样肯定是不严谨的,所以我使用 Symbol:

第二步(Symbol 优化)

symbol 是一种基本数据类型,每个从 Symbol() 返回的 symbol 值都是唯一的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.myCall = function (outObj, ...args) {
let key = Symbol("key");
//这里不能outObj.key,因为这样就如上述代码,没得变化了
outObj[key] = this;
//根据剩余参数,args为数组,所以需要...args
const res = outObj[key](...args);
delete outObj[key];
//最后返回原函数的返回结果
return res;
};
let obj = { name: "刘德华" };
function fn(num1, num2) {
console.log(this);
return num1 + num2;
}
const res = fn.myCall(obj, 1, 2);
console.log("两数之和为", res);

上述代码使用delete outObj[key]就是清除向对象里面追加的fn 函数。如下
image.png

第三步(考虑其他情况)

根据 call 方法:当你要改变 this 的指向为 bull 或 undefind 时,fn 函数的 this 指向将会变为Window
image.png
所以我有了自己的想法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Function.prototype.myCall = function (outObj, ...args) {
let key = Symbol("key");
//当你要改变this的指向为bull或undefind时
if (outObj === null || outObj === undefined) {
//突然想到Window也是对象,一葫芦画瓢
window[key] = this;
const res = window[key](...args);
delete window[key];
return res;
} else {
outObj[key] = this;
const res = outObj[key](...args);
//清除多余函数
delete outObj[key];
return res;
}
};
let obj = { name: "刘德华" };
function fn(num1, num2) {
console.log(this);
return num1 + num2;
}
const res = fn.myCall(obj, 1, 2);
console.log("两数之和为", res);

apply

apply 与 call 的区别就在于,传递的参数形式不同,掌握 call,对于 apply 也就差不多了。同样定义函数 myApply:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Function.prototype.myApply = function (outObj, arr) {
let key = Symbol("key");
if (outObj === null || outObj === undefined) {
window[key] = this;
const res = window[key](...arr);
delete window[key];
return res;
} else {
outObj[key] = this;
const res = outObj[key](...arr);
//清除多余函数
delete outObj[key];
return res;
}
};
let obj = { name: "xjy" };
function fn(num1, num2) {
console.log(this);
return num1 + num2;
}
const res = fn.myApply(undefined, [1, 2]);
console.log("两数之和为", res);

同样也考虑了改变 this 的指向为 bull 或 undefind 时的情况

bind

bind 可能有两种方式:

第一种

第一种就是上述 call 与 apply 的实现方式一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Function.prototype.myBind = function (outObj, ...a) {
let key = Symbol("key");
if (outObj === null || outObj === undefined) {
window[key] = this;
return (...b) => {
const res = window[key](...a, ...b);
delete window[key];
return res;
};
} else {
outObj[key] = this;
return (...b) => {
const res = outObj[key](...a, ...b);
delete outObj[key];
return res;
};
}
};
let obj = { name: "xjy" };
function fn(num1, num2, num3) {
console.log(this);
return num1 + num2 + num3;
}
const res = fn.myBind(null, 1, 2, 10);
console.log("两数之和为", res(3));

上述代码之所以 return 一个回调函数,是因为 bind 调用不是立即执行,然后后面传递的参数是优先于const res = fn.myBind(null, 1, 2, 10)后再console.log('两数之和为', res(3));所以有一定的顺序传递。

第二种

这种方式比较简便,在使用了一次 call 改变 this 指向。

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.myBind = function (outObj, ...a) {
return (...b) => {
//因为回调函数是箭头函数,所以this指向fn函数
return this.call(outObj, ...a, ...b);
};
};
let obj = { name: "xjy" };
function fn(num1, num2, num3) {
console.log(this);
return num1 + num2 + num3;
}
const res = fn.myBind(obj, 1, 2, 10);
console.log("两数之和为", res(3));

这里不需要考虑 null 与 undefind 的原因是,使用了 call 方法,上述已经讲过 call 本生改变 this 指向为 null 与 undefind 时,原函数的 this 就会指向 window。

最后

上述就是手写 call,apply 与 bind 的全部内容,本人也是观看视频写下的总结!,上述内容可能还有缺陷,欢迎指出。

  • 标题: 【手写系列】call,apply与bind以及详细解析
  • 作者: The局外人
  • 创建于 : 2024-05-14 11:43:01
  • 更新于 : 2023-09-05 21:48:40
  • 链接: https://dragon-xjy.gitee.io/2024/05/14/[手写系列]call,apply与bind以及详细解析/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论